/*
 * Copyright (c) 2018-2999 北京小象智慧信息科技有限公司 All rights reserved.
 *
 * https://www.xiaoxiangai.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.enums.SmsType;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.model.ShopDetail;
import com.yami.shop.bean.model.SmsLog;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.bean.ALiDaYu;
import com.yami.shop.common.enums.YamiHttpStatus;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.Json;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.dao.SmsLogMapper;
import com.yami.shop.service.OrderService;
import com.yami.shop.service.ShopDetailService;
import com.yami.shop.service.SmsLogService;
import com.yami.shop.service.UserService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;


/**
 * @author lgh on 2018/11/29.
 */
@Service
@Slf4j
@AllArgsConstructor
public class SmsLogServiceImpl extends ServiceImpl<SmsLogMapper, SmsLog> implements SmsLogService {

    private SmsLogMapper smsLogMapper;

    private ALiDaYu aLiDaYu;

    private static final String error ="000";//验证次数过多

    private static  final String error1 ="111";//验证码错误

    private static  final String error2 ="222";//验证码已过期

    private static  final String success="999";//验证成功

    /**
     * 产品名称:云通信短信API产品,开发者无需替换
     */
    private static final String PRODUCT = "Dysmsapi";
    /**
     * 产品域名,开发者无需替换
     */
    private static final String DOMAIN = "dysmsapi.aliyuncs.com";

    /**
     * 当天最大验证码短信发送量
     */
    private static final int TODAY_MAX_SEND_VALID_SMS_NUMBER = 10;

    /**
     * 一段时间内短信验证码的最大验证次数
     */
    private static final int TIMES_CHECK_VALID_CODE_NUM = 50;

    /**
     * 短信验证码的前缀
     */
    private static final String CHECK_VALID_CODE_NUM_PREFIX = "checkValidCodeNum_";

    /**
     * 找回密码短信验证码的前缀
     */
    private static final String CHECK_RETRIEVE_CODE_NUM_PREFIX = "checkRetrieveCodeNum_";

    /**
     * 验证码登陆的前缀
     */
    private static  final String CHECK_LOGIN_CODE_NUM_PREFIX ="checkLoginCode_";

    /**
     * 短信发送成功的标志
     */
    private static final String SEND_SMS_SUCCESS_FLAG = "OK";

    @Autowired
    private OrderService orderService;

    @Autowired
    private ShopDetailService shopDetailService;

    @Autowired
    private UserService userService;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean sendSms(SmsType smsType, String mobile, Map<String, String> params) {

        SmsLog smsLog = new SmsLog();
        if (smsType.equals(SmsType.REGISTRATIONCODE)) {
            int todaySendSmsNumber = smsLogMapper.selectCount(new LambdaQueryWrapper<SmsLog>()
                    .gt(SmsLog::getRecDate, DateUtil.beginOfDay(new Date()))
                    .lt(SmsLog::getRecDate, DateUtil.endOfDay(new Date()))
                    .eq(SmsLog::getUserPhone, mobile)
                    .eq(SmsLog::getType, smsType.value()));
            if (todaySendSmsNumber >= TODAY_MAX_SEND_VALID_SMS_NUMBER) {
                throw new YamiShopBindException("今日发送短信验证码次数已达到上限");
            }

            // 将上一条验证码失效
            smsLogMapper.invalidSmsByMobileAndType(mobile, smsType.value());

            String code = RandomUtil.randomNumbers(6);
            params.put("code", code);
        }
        smsLog.setType(smsType.value());
        smsLog.setMobileCode(params.get("code"));
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserPhone(mobile);
        smsLog.setContent(formatContent(smsType, params));
        smsLogMapper.insert(smsLog);
        try {
            this.sendSms(mobile, smsType.getTemplateCode(), params);
        } catch (ClientException e) {
            throw new YamiShopBindException("MissingAccessKeyId");
        }

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public String checkValidCode(String mobile, String code, SmsType smsType) {
        long checkValidCodeNum = RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
        if (checkValidCodeNum == 0) {
            // 半小时后失效
            RedisUtil.expire(CHECK_VALID_CODE_NUM_PREFIX, 600);
        }
        if (checkValidCodeNum >= TIMES_CHECK_VALID_CODE_NUM) {
            return error;
        }
        SmsLog sms = new SmsLog();
        sms.setUserPhone(mobile);
        sms.setMobileCode(code);
        sms.setStatus(1);
        sms.setType(smsType.value());

        SmsLog dbSms = smsLogMapper.selectOne(new LambdaQueryWrapper<SmsLog>()
                .eq(SmsLog::getUserPhone, mobile)
                .eq(SmsLog::getMobileCode, code)
                .eq(SmsLog::getStatus, 1)
                .eq(SmsLog::getType, smsType.value()));
        // 没有找到当前的验证码
        if (dbSms == null) {
            RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
            return error1;
        }
        RedisUtil.del(CHECK_VALID_CODE_NUM_PREFIX + mobile);
        // 标记为失效状态
        dbSms.setStatus(0);
        smsLogMapper.updateById(dbSms);
        // 验证码已过期
        DateTime offsetMinute = DateUtil.offsetMinute(dbSms.getRecDate(), 10);
        if (offsetMinute.getTime() < System.currentTimeMillis()) {
            RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
            return error2;
        }

        return success;
    }

    //当客户下订单后，短信通知商家
    @Override
    public void sendSmsToShop(String shopName, String mobile,String orderNumber,String adminMobile) {
        SmsLog smsLog = new SmsLog();
        Map  map = new HashMap();
        Map  map1 = new HashMap();
        map.put("orderNumber",orderNumber);
        smsLog.setType(3);
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserType('2');
        smsLog.setUserPhone(mobile);
        smsLog.setContent(formatContent(SmsType.NOTICETOSHOP, map)+"订单号为"+orderNumber);
        smsLogMapper.insert(smsLog);

        //发送请求给管理员  获取管理员的信息
        if(!adminMobile.equals(mobile)){
            SmsLog smsLog1 = new SmsLog();
            map1.put("shopName",shopName);
            map1.put("phoneNumber",mobile);
            smsLog1.setType(3);
            smsLog1.setRecDate(new Date());
            smsLog1.setStatus(1);
            smsLog1.setUserType('2');
            smsLog1.setUserPhone(adminMobile);
            smsLog1.setContent(formatContent(SmsType.NOTICETOADMIN, map1));
            smsLogMapper.insert(smsLog1);
        }
        try {
            this.sendSms(mobile, SmsType.NOTICETOSHOP.getTemplateCode(), map);
            if(!adminMobile.equals(mobile)){
                this.sendSms(adminMobile, SmsType.NOTICETOADMIN.getTemplateCode(), map1);
            }
        } catch (ClientException e) {
            throw new YamiShopBindException("发送短信失败，请稍后再试");
        }
    }

    //商家通过审核后,短信通知商家
    @Override
    public void sendSmsAfterPassing(String mobile) {
        SmsLog smsLog = new SmsLog();
        Map  map = new HashMap();
        smsLog.setType(3);
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserType('2');
        smsLog.setUserPhone(mobile);
        smsLog.setContent("恭喜您，您已通过审核成功入驻小二啷当平台，快去登录使用吧，祝您生意兴隆！");
        smsLogMapper.insert(smsLog);
        try {
            this.sendSms(mobile, SmsType.AFTERPASSING.getTemplateCode(), map);
        } catch (ClientException e) {
            throw new YamiShopBindException("发送短信失败，请稍后再试");
        }
    }

    //商家注册后,短信通知管理员进行审核
    @Override
    public void sendSmsToAmdin(String mobile) {
        SmsLog smsLog = new SmsLog();
        Map  map = new HashMap();
        smsLog.setType(3);
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserType('2');
        smsLog.setUserPhone(mobile);
        smsLog.setContent("有新的商家入驻啦，请尽快登入后台处理！");
        smsLogMapper.insert(smsLog);
        try {
            this.sendSms(mobile, SmsType.SENDSMSTOADMIN.getTemplateCode(), map);
        } catch (ClientException e) {
            throw new YamiShopBindException("发送短信失败，请稍后再试");
        }
    }

    /**
     * 会员消费后,发送会员消费提醒短信
     */
    public void sendSmsToMemberAfterConsumption(String mobile,String orderNumber){
        //拼接短信内容
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int month =calendar.get( Calendar.MONTH)+1;
        int day =calendar.get( Calendar.DATE);
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        //截取尾号后4位
        String number = mobile.substring(mobile.length()-4);
        //获取当前店铺名称
        Order order = orderService.getOrderByOrderNumber(orderNumber);
        String shopName=null;
        Double payMoney=null;
        String userId =null;
        Double userAccount=null;
        if(order!=null){
            Long shopId = order.getShopId();
            ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(shopId);
            if(shopDetail!=null){
                 shopName = shopDetail.getShopName();
            }
            payMoney = order.getActualTotal();
            userId = order.getUserId();
            User user = userService.getUserByUserId(userId);
            if(null!=user){
                userAccount =  user.getUserAccount();
            }
        }

        //拼接短信内容
        StringBuffer stringBuffer = new StringBuffer("尊敬的会员，您于(");
        stringBuffer.append(year).append("年").append(month).append("月").append(day).append("日").append(hour).append("点").append(minute).append("分)").append(number).append("在");
        stringBuffer.append(shopName).append("店铺消费").append(payMoney).append("元").append("，当前账户余额为").append(userAccount).append("元。感谢您的光临!");

        SmsLog smsLog = new SmsLog();
        smsLog.setType(3);
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserType('1');
        smsLog.setUserPhone(mobile);
        smsLog.setContent(stringBuffer.toString());
        smsLogMapper.insert(smsLog);
        Map map = new HashMap();
        map.put("year",year);
        map.put("month",month);
        map.put("day",day);
        map.put("hour",hour);
        map.put("minute",minute);
        map.put("phone",number);
        map.put("shop",shopName);
        map.put("amount",payMoney);
        map.put("balance",userAccount);
        try {
            this.sendSmsByXeld(mobile, SmsType.CONSUMPTIONSMS.getTemplateCode(), map);
        } catch (ClientException e) {
            throw new YamiShopBindException("发送短信失败，请稍后再试");
        }

    }

    private void sendSms(String mobile, String templateCode, Map<String, String> params) throws ClientException {

        //可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");

        //初始化acsClient,暂不支持region化
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", aLiDaYu.getAccessKeyId(), aLiDaYu.getAccessKeySecret());
        DefaultProfile.addEndpoint("cn-hangzhou", PRODUCT, DOMAIN);
        IAcsClient acsClient = new DefaultAcsClient(profile);

        //组装请求对象-具体描述见控制台-文档部分内容
        SendSmsRequest request = new SendSmsRequest();
        //必填:待发送手机号
        request.setPhoneNumbers(mobile);
        //必填:短信签名-可在短信控制台中找到
        request.setSignName("小二啷当美食商城");
        //必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(templateCode);
        request.setTemplateParam(Json.toJsonString(params));


        //hint 此处可能会抛出异常，注意catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        log.debug(Json.toJsonString(sendSmsResponse));
        if (sendSmsResponse.getCode() == null || !SEND_SMS_SUCCESS_FLAG.equals(sendSmsResponse.getCode())) {
            throw new YamiShopBindException("发送短信失败，请稍后再试:" + sendSmsResponse.getMessage());
        }
    }

    /**
     * 小二啷当签名,发送短信
     */
    private void sendSmsByXeld(String mobile, String templateCode, Map<String, String> params) throws ClientException {

        //可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");

        //初始化acsClient,暂不支持region化
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", aLiDaYu.getAccessKeyId(), aLiDaYu.getAccessKeySecret());
        DefaultProfile.addEndpoint("cn-hangzhou", PRODUCT, DOMAIN);
        IAcsClient acsClient = new DefaultAcsClient(profile);

        //组装请求对象-具体描述见控制台-文档部分内容
        SendSmsRequest request = new SendSmsRequest();
        //必填:待发送手机号
        request.setPhoneNumbers(mobile);
        //必填:短信签名-可在短信控制台中找到
        request.setSignName("小二啷当");
        //必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(templateCode);
        request.setTemplateParam(Json.toJsonString(params));


        //hint 此处可能会抛出异常，注意catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        log.debug(Json.toJsonString(sendSmsResponse));
        if (sendSmsResponse.getCode() == null || !SEND_SMS_SUCCESS_FLAG.equals(sendSmsResponse.getCode())) {
            throw new YamiShopBindException("发送短信失败，请稍后再试:" + sendSmsResponse.getMessage());
        }
    }



    /**
     * 找回密码发送验证码
     * @param smsType
     * @param mobile
     * @param params
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean sendRetrievePassword(SmsType smsType, String mobile, Map<String, String> params) {

        SmsLog smsLog = new SmsLog();
        if (smsType.equals(SmsType.RETRIEVECODE)) {
            int todaySendSmsNumber = smsLogMapper.selectCount(new LambdaQueryWrapper<SmsLog>()
                    .gt(SmsLog::getRecDate, DateUtil.beginOfDay(new Date()))
                    .lt(SmsLog::getRecDate, DateUtil.endOfDay(new Date()))
                    .eq(SmsLog::getUserPhone, mobile)
                    .eq(SmsLog::getType, smsType.value()));
            if (todaySendSmsNumber >= TODAY_MAX_SEND_VALID_SMS_NUMBER) {
                throw new YamiShopBindException("今日发送短信验证码次数已达到上限");
            }

            // 将上一条验证码失效
            smsLogMapper.invalidSmsByMobileAndType(mobile, smsType.value());

            String code = RandomUtil.randomNumbers(6);
            params.put("code", code);
        }
        smsLog.setType(smsType.value());
        smsLog.setMobileCode(params.get("code"));
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserPhone(mobile);
        smsLog.setContent(formatContent(smsType, params));
        smsLogMapper.insert(smsLog);
        try {
            this.sendSms(mobile, smsType.getTemplateCode(), params);
        } catch (ClientException e) {
            throw new YamiShopBindException("发送短信失败，请稍后再试");
        }

        return true;
    }

    /**
     * 找回密码校验验证码
     * @param mobile
     * @param code
     * @param smsType
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public String sendSmsToRetrieve(String mobile, String code, SmsType smsType) {
        long checkRetrieveCodeNum = RedisUtil.incr(CHECK_RETRIEVE_CODE_NUM_PREFIX + mobile, 1);
        if (checkRetrieveCodeNum == 0) {
            // 半小时后失效
            RedisUtil.expire(CHECK_RETRIEVE_CODE_NUM_PREFIX, 600);
        }
        if (checkRetrieveCodeNum >= TIMES_CHECK_VALID_CODE_NUM) {
            return error;
        }
        SmsLog sms = new SmsLog();
        sms.setUserPhone(mobile);
        sms.setMobileCode(code);
        sms.setStatus(1);
        sms.setType(smsType.value());

        SmsLog dbSms = smsLogMapper.selectOne(new LambdaQueryWrapper<SmsLog>()
                .eq(SmsLog::getUserPhone, mobile)
                .eq(SmsLog::getMobileCode, code)
                .eq(SmsLog::getStatus, 1)
                .eq(SmsLog::getType, smsType.value()));
        // 没有找到当前的验证码
        if (dbSms == null) {
            RedisUtil.incr(CHECK_RETRIEVE_CODE_NUM_PREFIX + mobile, 1);
            return error1;
        }
        RedisUtil.del(CHECK_RETRIEVE_CODE_NUM_PREFIX + mobile);
        // 标记为失效状态
        dbSms.setStatus(0);
        smsLogMapper.updateById(dbSms);
        // 验证码已过期
        DateTime offsetMinute = DateUtil.offsetMinute(dbSms.getRecDate(), 10);
        if (offsetMinute.getTime() < System.currentTimeMillis()) {
            RedisUtil.incr(CHECK_RETRIEVE_CODE_NUM_PREFIX + mobile, 1);
            return error2;
        }

        return success;
    }

    @Override
    public boolean loginBySendSms(String mobile) {
        Map map = new HashMap();
        String code = RandomUtil.randomNumbers(6);
        SmsLog smsLog = new SmsLog();
        smsLog.setType(4);
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserType('2');
        smsLog.setUserPhone(mobile);
        smsLog.setMobileCode(code);
        map.put("code",code);
        smsLog.setContent("验证码"+code+",您正在使用短信验证码登陆，有效期十分钟。");
        smsLogMapper.insert(smsLog);
        try {
            this.sendSms(mobile, SmsType.LOGINBYSENDSMS.getTemplateCode(), map);
        } catch (ClientException e) {
            throw new YamiShopBindException("发送短信失败，请稍后再试");
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public String checkLoginCode(String mobile, String code, SmsType smsType) {
        long checkValidCodeNum = RedisUtil.incr(CHECK_LOGIN_CODE_NUM_PREFIX + mobile, 1);
        if (checkValidCodeNum == 0) {
            // 半小时后失效
            RedisUtil.expire(CHECK_LOGIN_CODE_NUM_PREFIX, 600);
        }
        if (checkValidCodeNum >= TIMES_CHECK_VALID_CODE_NUM) {
            return error;
        }
        SmsLog sms = new SmsLog();
        sms.setUserPhone(mobile);
        sms.setMobileCode(code);
        sms.setStatus(1);
        sms.setType(4);

        SmsLog dbSms = smsLogMapper.selectOne(new LambdaQueryWrapper<SmsLog>()
                .eq(SmsLog::getUserPhone, mobile)
                .eq(SmsLog::getMobileCode, code)
                .eq(SmsLog::getStatus, 1)
                .eq(SmsLog::getType, 4));
        // 没有找到当前的验证码
        if (dbSms == null) {
            RedisUtil.incr(CHECK_LOGIN_CODE_NUM_PREFIX + mobile, 1);
            return error1;
        }
        RedisUtil.del(CHECK_LOGIN_CODE_NUM_PREFIX + mobile);
        // 标记为失效状态
        dbSms.setStatus(0);
        smsLogMapper.updateById(dbSms);
        // 验证码已过期
        DateTime offsetMinute = DateUtil.offsetMinute(dbSms.getRecDate(), 10);
        if (offsetMinute.getTime() < System.currentTimeMillis()) {
            RedisUtil.incr(CHECK_LOGIN_CODE_NUM_PREFIX + mobile, 1);
            return error2;
        }
        return  success;
    }

    private String formatContent(SmsType smsType, Map<String, String> params) {
        if (CollectionUtil.isEmpty(params)) {
            return smsType.getContent();
        }
        String content = smsType.getContent();
        for (Entry<String, String> element : params.entrySet()) {
            content = content.replace("${" + element.getKey() + "}", element.getValue());
        }
        return content;
    }
}
